Skip to content

feat(runs): persisted run history (T-K1) - #442

Merged
lightningpixel merged 3 commits into
devfrom
feat/run-history
Nov 13, 2026
Merged

lightningpixel merged 3 commits into
devfrom
feat/run-history

Conversation

@lightningpixel

Copy link
Copy Markdown
Owner

Summary

Runs now leave a persisted record that survives a restart: generations, workflows and mesh ops, including failed, cancelled and interrupted ones (T-K1).

Refs #411

Record shape

  • Defined in api/schemas/runs.py (RunRecord) and mirrored in src/shared/types/runs.ts. Fields:
    • id, kind (generation | workflow | mesh_op);
    • status (pending | running | done | error | cancelled | interrupted);
    • model / workflow / op ids;
    • inputs, params, timed steps[], error, output_path;
    • created_at, started_at, ended_at, and a computed duration_s.
  • This is the shape provenance (T-F1) should reuse, so there is no second store.

Storage (api/services/run_history.py)

  • One JSON file at the workspace root, .modly-run-history.json. It is hidden from collections and the asset library, and it follows the workspace if it moves.
  • Writes are atomic (temp file + fsync + os.replace).
  • A missing file counts as an empty history. A corrupt file is set aside as .corrupt-<ts>.
  • Retention cap: 500 records, oldest finished runs dropped first; at most 200 steps per run.
  • Disk errors are logged and never raised, so the history can't break a generation.
  • Runs still pending or running at shutdown, or found after a crash, become interrupted.

What gets recorded

  • Generations: /generate/from-image, /generate/from-artifact and /workflow-runs/from-image, recorded as they are queued, start and end. Cancels are recorded immediately. Progress labels become timed steps.
  • Mesh ops: /optimize/op/{id}, /optimize/mesh and /optimize/smooth. Report-only ops (inspect_mesh, suggest_fixes) are skipped.
  • Workflows: the renderer reports these through PUT /workflow-runs/{id}, with one timed step per node. Writes are serialized so the final status always wins.

Endpoints (existing /workflow-runs router)

  • GET /workflow-runs?status=&kind=&limit=&offset=: newest first, repeatable filters, limit at most 200.
  • PUT /workflow-runs/{id}: creates or replaces a workflow record. Returns 409 if the id belongs to a backend-owned run.
  • GET /workflow-runs/{id}: falls back to the history once the live job has been purged or the app restarted.

Known limits

  • No Queue UI yet; that is T-K2.
  • A workflow's model nodes also create their own generation records, not linked to the parent workflow record.
  • /transform, /arrange and /patch-vertices are not recorded.
  • interrupted runs have no ended_at or duration, since the real stop time is unknown.

Test plan

  • python -m unittest discover -s tests: 27 new tests (store lifecycle, paging, filters, retention, restart and shutdown interruption, corrupt/missing file, atomic writes, endpoints, generation done/error/cancel, mesh-op recording)
  • npm run test:node: 8 new tests for the workflow record helpers
  • npm run lint
  • In the app:
    • run one generation that succeeds, one that fails and one that is cancelled;
    • check GET /workflow-runs for each status and duration;
    • quit during a generation and confirm it is listed as interrupted after a restart.

One RunRecord per generation, workflow or mesh-op run, kept in a JSON file at
the workspace root. Writes are atomic, a corrupt file is moved aside, runs a
previous backend left in flight are marked interrupted on load, and the
history is capped. The record shape is the one provenance (T-F1) will reuse.
…ow-runs

Generation jobs are recorded when filed, started and ended (cancel included),
with their step timings. Mesh ops that write a mesh or fail are recorded too.
GET /workflow-runs pages the history with status/kind filters, PUT
/workflow-runs/{id} lets the renderer report workflow runs, and
GET /workflow-runs/{id} falls back to the history once a job is purged.
The workflow runner reports its run record (per-node step timings, final
status, output) to PUT /workflow-runs/{id}, chaining writes so the final
status always lands last. Cancelled and abandoned runs are recorded as
cancelled.
@lightningpixel
lightningpixel merged commit a48a077 into dev Nov 13, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant